﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using log4net;
using TomanuExtensions;

namespace AmiEmuLib.Host.HostInput
{
    /// <summary>
    /// Base class from devices grouped by type.
    /// </summary>
    public abstract class HostInputDevices
    {
        /// <summary>
        /// Logger.
        /// </summary>
        private static readonly ILog Logger =
            LogManager.GetLogger(typeof(HostInputDevicesList).GetParentFullName());

        /// <summary>
        /// Devices properties changed. 
        /// </summary>
        public event Action OnChanged;

        /// <summary>
        /// Events to generate.
        /// </summary>
        protected InputEvent[] m_events;

        /// <summary>
        /// Host event to generate.
        /// </summary>
        protected HostEvent[] m_host_events;

        /// <summary>
        /// Buttons or keys states. We need this to deal with window focus.
        /// </summary>
        protected bool[] m_states;

        /// <summary>
        /// Generated on every event. No information about how value was changed, 
        /// fit for GUI events mapping.
        /// </summary>
        internal event InputEvent OnEvent;

        /// <summary>
        /// Constructor.
        /// </summary>
        public HostInputDevices()
        {
            m_events = new InputEvent[GetHostEvents().Events.Max(ev => ev.ID) + 1];
            m_host_events = new HostEvent[GetHostEvents().Events.Max(ev => ev.ID) + 1];

            for (int i = 0; i < m_events.Length; i++)
                m_host_events[i] = GetHostEvents().Events.FirstOrDefault(ev => ev.ID == i);

            m_states = new bool[m_host_events.Length];
        }

        /// <summary>
        /// Something changed. Generate event. Use by devices rescan.
        /// </summary>
        internal void Changed()
        {
            if (OnChanged != null)
                OnChanged();
        }

        /// <summary>
        /// Generat event to listeners.
        /// </summary>
        /// <param name="Event_ID_Move_Relative_Y"></param>
        /// <param name="p"></param>
        /// <param name="a_y"></param>
        protected void GenerateEvent(int a_event_id, bool a_pushed, int a_value)
        {
            if (m_events[a_event_id] != null)
                m_events[a_event_id](m_host_events[a_event_id], a_pushed, a_value);

            if (OnEvent != null)
                OnEvent(m_host_events[a_event_id], a_pushed, a_value);
        }

        /// <summary>
        /// Listen for this event.
        /// </summary>
        /// <param name="a_event"></param>
        /// <param name="a_listener"></param>
        /// <returns></returns>
        internal bool RegisterEvent(HostEvent a_event, InputEvent a_listener)
        {
            int index = m_host_events.IndexOf(a_event);

            if (index == -1)
                return false;

            m_events[index] += a_listener;

            return true;
        }

        /// <summary>
        /// Stop listening for this event.
        /// </summary>
        /// <param name="a_event"></param>
        /// <param name="a_listener"></param>
        /// <returns></returns>
        internal bool UnregisterEvent(HostEvent a_event, InputEvent a_listener)
        {
            int index = m_host_events.IndexOf(a_event);

            if (index == -1)
                return false;

            InputEvent ie = m_events[index];

            if (!ie.GetInvocationList().Contains(a_listener))
            {
                Logger.ErrorFormat("Event '{0}' was never registered for '{1}'", 
                    a_event.Name, GetType().Name);
                return true;
            }

            ie -= a_listener;

            return true;
        }

        /// <summary>
        /// Starts processing input information.
        /// </summary>
        internal abstract void Connect();

        /// <summary>
        /// Stop processing input information.
        /// </summary>
        internal abstract void Disconnect();

        /// <summary>
        /// Return all host events for this devices class.
        /// </summary>
        /// <returns></returns>
        protected abstract HostEvents GetHostEvents();

        /// <summary>
        /// Clear all devices. Use by devices rescan.
        /// </summary>
        internal abstract void ClearDevices();
    }

    /// <summary>
    /// Base class from devices grouped by type.
    /// </summary>
    public abstract class HostInputDevices<T> : HostInputDevices where T : HostInputDevice
    {
        /// <summary>
        /// List of all keyboards connected to host. This list never change.
        /// </summary>
        protected List<T> m_devices = new List<T>();

        /// <summary>
        /// Add device. Use by devices rescan.
        /// </summary>
        /// <param name="a_device"></param>
        internal void AddDevice(T a_device) 
        {
            m_devices.Add(a_device);
        }

        internal override void ClearDevices()
        {
            m_devices.Clear();
        }

        /// <summary>
        /// List of all devices.
        /// </summary>
        public IEnumerable<T> Devices
        {
            get
            {
                return m_devices;
            }
        }
    }
}
